昨天提到,開發者都是在第一線直接被技術債凌虐,是最有感覺的苦主。
在談技術債的時候曾說過:「事後被別人發現的才叫 bug ,自己開發當下發現的不算」,技術債也是,設計不良的做法,當下覺得不對勁就可以立即重構改善設計;如果不重構,在未來被其他人抱怨時,就會成為技術債。
這不對勁的感覺,在重構這本書裡稱之為「壞味道」。壞味道也是一種隱喻,大概就像是:第一眼看到程式碼時,先皺個眉頭,接著歪著頭,一手托著下巴,另一手操作程式碼畫面上下移動,最後語重心長地「嗯」了一聲。相信旁邊的同事放屁也會有一樣很不舒服的感覺。
壞味道也很像另一種類似的概念:反模式(Anti-patterns)
以下會使用 PHP 語言,列幾個經典的壞味道,同時也會解釋為何會很不舒服,與比較好的解決方法。
第一次跟爛 code 相見歡的時候,看到大括號有時在上有時在下:
function bar()
{
echo 'Hello World';
}
function foo() {
echo 'Hello World';
}
空白 tab 傻傻分不清楚:
$hello = 'Hello';
if ($hello === '') {
echo 'Hello World';
} else {
echo $hello . ' World';
}
運算子與運算元有時有間隔,有時黏在一起:
$world = 'World';
echo 'Hello ' . $str;
echo 'Hello '.$str;
函式或變數的命名有時小駝峰,有時候又底線分隔,還有全小寫擠在一起的是來亂的嗎?
$fooBar = 'Hello ';
$bar_foo = 'World';
$helloworldfoobar = $fooBar . $bar_foo;
echo $helloworldfoobar;
在閱讀程式碼的過程中,這些不一致的程式碼,會不定時讓開發者的思緒中斷,非常不舒服。
這是因為風格不一致所造成的。這就像三十天鐵人賽交給三十個寫作高手寫,會因為每個人寫作風格不同造成閱讀上的困難,反而會不如一個人寫三十天來的好。
排版是有工具可以自動化解決的,如常見的 IDE 或 Composer 上都能找得到對應的工具,但命名規則工具就只能做到檢查,修改只能靠手動。
最近在翻舊程式看到這段程式碼:
$params = [
'a' => 1,
'b' => 2,
'c' => 3,
];
$query = '';
foreach ($params as $key => $value) {
if ('' === $query) {
$query .= $key . '=' . $value;
} else {
$query .= '&' . $key . '=' . $value;
}
}
仔細看程式碼並思考一下,其實不難理解,這是在組超連結的 query 。於是,在需要組超連結的時候,就會看到這段 foreach
的程式碼。
事實上,程式碼不難懂,但每次閱讀程式碼的時候,都需要停下來花點時間思考這段程式碼的用途;而且程式碼的內容並不算少,如果裡面有一點點差異,是很難發現的。如下面這段程式碼乍看之下跟上面的程式碼是一樣的,但事實上是有 bug 的。
$query = '';
foreach ($params as $key => $value) {
if ('' === $query) {
$query .= '&' . $key . '=' . $value;
} else {
$query .= $key . '=' . $value;
}
}
如果有踩過這種雷,那停下來思考的時間就會更久,思緒中斷的會非常強烈,感覺也更不舒服。
上述問題的標準解是使用 PHP 內建函式 http_build_query()
:
$query = http_build_query($params);
是不是簡單許多了?
如果是有點像又不太像的重複程式碼,未來有機會的話,將會示範如何重構。
Method 太肥的狀況是這樣的:
function foo($params) {
$result = [];
$result[] = $params['bar'];
//
// 中間想像有 100 行程式碼
//
$result[] = $bar;
return $result;
}
這樣的 method 最大的問題在 $result
最一開頭就定義,中間 100 行程式碼中,可能都有操作這個變數。所以當遇到需要了解這個 method ,比方說 return 結果不對時,很有可能需要把這 100 行可能操作回傳值的程式碼全部看完,才會找到問題所在。
另一個問題是, method 越大做的事越多,代表依賴也越多;依賴多就有可能會發生:依賴 A 了解完後,要了解依賴 B 時,會有 context 切換,因此會有思緒中斷,這種的感覺也是很不舒服。
Class 過肥的情況也是類似的。
Scrum 通常會先有 story ,然後開始切 task 再開始工作。 Story 可能很大,跟一個 100 行的 method 一樣大,所以我們 method 應該是有辦法切成小 method 來組合出主 method 要的功能。